package wowcatsim;

import wowcatsim.interfaces.CatStrategy;
import wowcatsim.strategy.DoNothingStrategy;
import wowcatsim.utils.MersenneTwisterFast;

public class SimulatorJob extends Thread {

    public enum Action {

        NONE, RAKE, MANGLE, SHRED, RIP, BITE, ROAR, FURY, BERSERK
    };

    public enum AttackResult {

        HIT, MISS, DODGE, GLANCE, CRIT
    };
    private int simTimer = -300;
    private int mangleTimer = 0;
    private int rakeTimer = 0;
    private int roarTimer = 0;
    private int ripTimer = 0;
    private int furyTimer = 0;
    private int furyCooldown = 0;
    private int berserkTimer = 0;
    private int berserkCooldown = 0;
    private boolean oocUp = false;
    private boolean mangleUp = false;
    private boolean bleedUp = false;
    private int energy = 0;
    private int comboPoints = 0;
    private int rakeCost = 40;
    private int mangleCost = 45;
    private int shredCost = 60;
    private double ripAP = 0;
    private double rakeAP = 0;
    private Simulator simulator;
    private CatStrategy strategy = new DoNothingStrategy();
    private CatStatistics catStats = new CatStatistics();
    private MersenneTwisterFast random = new MersenneTwisterFast();
    private SimulatorSettings settings = new SimulatorSettings();
    private Equipment equip = new Equipment();
    private int jobId;
    private boolean logRun = false;
    private double damage = 0;

    public class SimStatus {

        private SimStatus() {
        }

        public int getSimTimer() {
            return simTimer;
        }

        public int getMangleTimer() {
            return mangleTimer;
        }

        public int getRakeTimer() {
            return rakeTimer;
        }

        public int getRipTimer() {
            return ripTimer;
        }

        public int getRoarTimer() {
            return roarTimer;
        }

        public int getFuryTimer() {
            return furyTimer;
        }

        public int getFuryCooldown() {
            return furyCooldown;
        }

        public int getComboPoints() {
            return comboPoints;
        }

        public int getEnergy() {
            return energy;
        }

        public boolean isOocUp() {
            return oocUp;
        }

        public boolean isMangleUp() {
            return mangleUp;
        }

        public boolean isBleedUp() {
            return bleedUp;
        }

        public int getRakeCost() {
            return rakeCost;
        }

        public int getMangleCost() {
            return mangleCost;
        }

        public int getShredCost() {
            return shredCost;
        }
    }

    public SimulatorJob(Simulator sim, int jobId) {
        simulator = sim;
        this.jobId = jobId;
        random.setSeed(jobId);
    }

    public void setStrategy(CatStrategy strat) {
        strategy = strat;
    }

    public void setStatistics(CatStatistics stats) {
        catStats = stats;
    }

    public void setSettings(SimulatorSettings s) {
        settings = s;
    }

    public void setLog(boolean b) {
        logRun = b;
    }

    public void setEquipment(Equipment equip) {
        this.equip = equip;
    }

    public void log(String str) {
        simulator.log("[Time=" + simTimer + "][ComboPts=" + comboPoints + "][Energy=" + energy + "]" + str + "[TotalDamage=" + damage + "]");
    }

    @Override
    public void run() {
        int ripCP = 0;

        double nextAutoAttack = settings.StartTime;
        int globalCooldownTimer = 0;

        double naturalist = 1.0 + 0.02 * catStats.Naturalist;
        double critDamage;
        switch (catStats.PredatoryInstincts) {
            case 3:
                critDamage = 2.20;
                break;

            case 2:
                critDamage = 2.14;
                break;

            case 1:
                critDamage = 2.06;
                break;

            default:
                critDamage = 2.0;
                break;
        }
        double dmgMod = 1 - settings.DamageReduction;

        // Talents reducing costs
        rakeCost -= catStats.Ferocity;
        mangleCost -= catStats.Ferocity;
        mangleCost -= 2 * catStats.ImprovedMangle;
        if (equip.T6Bonus) {
            mangleCost -= 5;
        }
        shredCost -= 9 * catStats.ShreddingAttacks;

        strategy.setStatus(new SimStatus());

        for (simTimer = settings.StartTime; simTimer < settings.MaxTimer; simTimer++) {
            double attackPower = catStats.attackPower;
            int plusDamage = catStats.plusDamage;
            mangleUp = settings.MangleUp;
            bleedUp = settings.BleedUp;

            // Reset damage counter at time = 0;
            if (simTimer == 0) {
                damage = 0;
            }
            if (roarTimer > 0) {
                roarTimer--;
                attackPower *= 1.40;
                if (logRun && roarTimer == 0) {
                    log("[Savage Roar][Fades]");
                }
            }

            if (furyTimer > 0) {
                furyTimer--;
                plusDamage += 131;
                if (logRun && furyTimer == 0) {
                    log("[Tiger's Fury][Fades]");
                }
            }

            if (furyCooldown > 0) {
                furyCooldown--;
                if (logRun && furyCooldown == 0) {
                    log("[Tiger's Fury][CooldownUp]");
                }
            }

            if (mangleTimer > 0) {
                mangleUp = true;
                mangleTimer--;
                if (logRun && mangleTimer == 0) {
                    log("[Mangle][Fades]");
                }
            }

            if (rakeTimer > 0) {
                rakeTimer--;
                bleedUp = true;
                if (rakeTimer % 30 == 0) {
                    // Rake tick
                    double rake = naturalist * (0.06 * rakeAP + 387) * (1 + catStats.SavageFury * 0.1);
                    if (mangleUp) {
                        rake *= 1.3;
                    }
                    damage += rake;
                    if (logRun) {
                        log("[Rake][Ticks][RakeTimer=" + rakeTimer + "][RakeDamage=" + rake + "]");
                    }
                }
            }

            if (ripTimer > 0) {
                bleedUp = true;
                ripTimer--;
                if (ripTimer % 20 == 0) {
                    // Rip tick
                    double rip = naturalist * (39 + (99 + 0.01 * ripAP) * ripCP);
                    if (mangleUp) {
                        rip *= 1.3;
                    }
                    damage += rip;
                    if (logRun) {
                        log("[Rip][Ticks][RipTimer=" + ripTimer + "][RipDamage=" + rip + "]");
                    }
                }
            }

            // Handle auto attacks
            if (nextAutoAttack <= simTimer) {
                AttackResult result = autoAttack();
                double attack = dmgMod * naturalist * (plusDamage + (attackPower + 768) / 14);
                switch (result) {
                    case HIT:
                        if (random.nextDouble() < (settings.OOC_CHANCE * catStats.OmensOfClarity)) {
                            oocUp = true;
                            if (logRun) {
                                log("[Omens of Clarity][Up]");
                            }
                        }
                        damage += attack;
                        if (logRun) {
                            log("[AutoAttack][Hits][AutoAttackDmg=" + attack + "]");
                        }
                        break;

                    case CRIT:
                        if (random.nextDouble() < (settings.OOC_CHANCE * catStats.OmensOfClarity)) {
                            oocUp = true;
                            if (logRun) {
                                log("[Omens of Clarity][Up]");
                            }
                        }
                        attack *= critDamage;
                        damage += attack;
                        if (logRun) {
                            log("[AutoAttack][CRITS][AutoAttackDmg=" + attack + "]");
                        }
                        break;

                    case GLANCE:
                        if (random.nextDouble() < (settings.OOC_CHANCE * catStats.OmensOfClarity)) {
                            oocUp = true;
                            if (logRun) {
                                log("[Omens of Clarity][Up]");
                            }
                        }
                        attack *= settings.GLANCE_MODIFIER;
                        damage += attack;
                        if (logRun) {
                            log("[AutoAttack][Glance][AutoAttackDmg=" + attack + "]");
                        }
                        break;

                    default:
                        break;
                }

                nextAutoAttack += 1000.0 / (100 + catStats.hastePercent);
            }

            // Call strategy
            if (globalCooldownTimer == 0 || furyCooldown == 0) {
                AttackResult result = yellowAttack();
                Action action = strategy.getAction();
                if (action == Action.FURY && furyCooldown == 0) {
                    energy += 20 * catStats.KingOfTheJungle;
                    if (energy > 100) {
                        if (logRun) {
                            log("[Energy Wasted]");
                        }
                        energy = 100;
                    }
                    furyCooldown = 300;
                    furyTimer = 60;
                    if (logRun) {
                        log("[Tiger's Fury][Up]");
                    }
                    action = strategy.getAction();
                }
                switch (action) {
                    case MANGLE:
                        if (energy < mangleCost && !oocUp) {
                            break;
                        }
                        double mangle = 2 * (plusDamage + (attackPower + 768)) / 14 + 634;
                        mangle *= dmgMod * naturalist * (1 + catStats.SavageFury * 0.1);
                        switch (result) {
                            case HIT:
                                damage += mangle;
                                if (!oocUp) {
                                    energy -= mangleCost;
                                }
                                mangleTimer = 120;
                                if (equip.glyphOfMangle) {
                                    mangleTimer += 60;
                                }
                                comboPoints++;
                                if (logRun) {
                                    log("[Mangle][Hits][MangleDmg=" + mangle + "]");
                                }
                                break;

                            case CRIT:
                                damage += critDamage * mangle;
                                if (!oocUp) {
                                    energy -= mangleCost;
                                }
                                mangleTimer = 180;
                                if (equip.glyphOfMangle) {
                                    mangleTimer += 60;
                                }
                                yellowCrit();
                                if (logRun) {
                                    log("[Mangle][CRITS][MangleDmg=" + critDamage * mangle + "]");
                                }
                                break;

                            default:
                                if (logRun) {
                                    log("[Mangle][Fails]");
                                }
                                if (!oocUp) {
                                    energy -= 0.3 * mangleCost;
                                }
                                break;
                        }
                        if (oocUp) {
                            oocUp = false;
                            if (logRun) {
                                log("[Omens of Clarity][Consumed]");
                            }
                        }
                        globalCooldownTimer = 10;
                        break;

                    case SHRED:
                        if (energy < shredCost && !oocUp) {
                            break;
                        }
                        double shred = 2.25 * (plusDamage + (attackPower + 768) / 14) + 742.5;
                        shred *= dmgMod * naturalist;
                        if (mangleUp) {
                            shred *= 1.3;
                        }
                        if (bleedUp) {
                            shred *= 1.0 + 0.04 * catStats.RendAndTear;
                        }
                        switch (result) {
                            case HIT:
                                damage += shred;
                                if (!oocUp) {
                                    energy -= shredCost;
                                }
                                comboPoints++;
                                if (logRun) {
                                    log("[Shred][Hits][ShredDmg=" + shred + "]");
                                }
                                break;

                            case CRIT:
                                damage += critDamage * shred;
                                if (!oocUp) {
                                    energy -= shredCost;
                                }
                                yellowCrit();
                                if (logRun) {
                                    log("[Shred][CRITS][ShredDmg=" + shred * critDamage + "]");
                                }
                                break;

                            default:
                                if (logRun) {
                                    log("[Shred][Fails]");
                                }
                                if (!oocUp) {
                                    energy -= 0.3 * mangleCost;
                                }
                                break;
                        }
                        if (oocUp) {
                            oocUp = false;
                            if (logRun) {
                                log("[Omens of Clarity][Consumed]");
                            }
                        }
                        globalCooldownTimer = 10;
                        break;

                    case RAKE:
                        if (energy < rakeCost) {
                            break;
                        }
                        double rake = dmgMod * naturalist * (190 + attackPower / 100) * (1 + catStats.SavageFury * 0.1);
                        if (mangleUp) {
                            rake *= 1.3;
                        }
                        switch (result) {
                            case HIT:
                                damage += rake;
                                if (!oocUp) {
                                    energy -= rakeCost;
                                }
                                comboPoints++;
                                rakeTimer = 90;
                                rakeAP = attackPower;
                                if (logRun) {
                                    log("[Rake][Hits][RakeDmg=" + rake + "][RakeAP=" + rakeAP + "]");
                                }
                                break;

                            case CRIT:
                                damage += critDamage * rake;
                                if (!oocUp) {
                                    energy -= rakeCost;
                                }
                                yellowCrit();
                                rakeTimer = 90;
                                rakeAP = attackPower;
                                if (logRun) {
                                    log("[Rake][CRITS][RakeDmg=" + rake * critDamage + "][RakeAP=" + rakeAP + "]");
                                }
                                break;

                            default:
                                if (logRun) {
                                    log("[Rake][Fails]");
                                }
                                if (!oocUp) {
                                    energy -= 0.3 * rakeCost;
                                }
                                break;
                        }
                        if (oocUp) {
                            oocUp = false;
                            if (logRun) {
                                log("[Omens of Clarity][Consumed]");
                            }
                        }
                        globalCooldownTimer = 10;
                        break;

                    case BITE:
                        if (energy < 35 && !oocUp) {
                            break;
                        }
                        switch (result) {
                            case HIT:
                            case CRIT:
                                if (!oocUp) {
                                    energy -= 35;
                                }
                                double bite = (290 + 0.07 * attackPower) * comboPoints + 120 + (9.4 + attackPower / 410) * energy;
                                bite *= (dmgMod * naturalist);
                                bite *= (1.0 + 0.03 * catStats.FeralAggression);

                                // Recalculate crit%
                                double bitecrit = catStats.critPercent / 100;
                                if (bleedUp) {
                                    bitecrit += 0.1 * catStats.RendAndTear;
                                }
                                if (random.nextDouble() < bitecrit) {
                                    bite *= critDamage;
                                    if (logRun) {
                                        log("[FerociousBite][CRITS][FerociousBiteDmg=" + bite + "]");
                                    }
                                } else {
                                    if (logRun) {
                                        log("[FerociousBite][Hits][FerociousBiteDmg=" + bite + "]");
                                    }
                                }
                                damage += bite;
                                energy = 0;
                                comboPoints = 0;
                                break;

                            default:
                                if (logRun) {
                                    log("[FerociousBite][Fails]");
                                }
                                if (!oocUp) {
                                    energy -= (1.0 - 0.4 * catStats.PrimalPrecision) * 35;
                                }
                                break;
                        }

                        if (oocUp) {
                            oocUp = false;
                            if (logRun) {
                                log("[Omens of Clarity][Consumed]");
                            }
                        }
                        globalCooldownTimer = 10;
                        break;

                    case RIP:
                        if (energy < 30 && !oocUp) {
                            break;
                        }
                        switch (result) {
                            case HIT:
                            case CRIT:
                                ripCP = comboPoints;
                                comboPoints = 0;
                                ripTimer = 120;
                                if (equip.glyphOfRip) {
                                    ripTimer += 40;
                                }
                                if (equip.T7Bonus) {
                                    ripTimer += 20;
                                }
                                if (!oocUp) {
                                    energy -= 30;
                                }
                                ripAP = attackPower;
                                if (logRun) {
                                    log("[Rip][Hits][RipComboPts=" + ripCP + "][RipAttackPower=" + ripAP + "]");
                                }
                                break;

                            default:
                                if (logRun) {
                                    log("[Rip][Fails]");
                                }
                                if (!oocUp) {
                                    energy -= (1.0 - 0.4 * catStats.PrimalPrecision) * 30;
                                }
                                break;
                        }
                        oocUp = false;
                        globalCooldownTimer = 10;
                        break;

                    case ROAR:
                        if (comboPoints < 1) {
                            break;
                        }
                        if (energy < 25 && !oocUp) {
                            break;
                        }
                        if (!oocUp) {
                            energy -= 25;
                        } else {
                            oocUp = false;
                            if (logRun) {
                                log("[Omens of Clarity][Consumed]");
                            }
                        }
                        roarTimer = 90 + comboPoints * 50;
                        if (logRun) {
                            log("[SavageRoar][Up][SavageRoarPts=" + comboPoints + "][SavageRoarDuration=" + roarTimer + "]");
                        }
                        comboPoints = 0;
                        globalCooldownTimer = 10;
                        break;

                    default:
                        break;
                }
            }

            if (comboPoints > 5) {
                comboPoints = 5;
            }
            if (globalCooldownTimer > 0) {
                globalCooldownTimer--;
            }
            energy++;
            if (energy > 100) {
                energy = 100;
                if (logRun) {
                    log("[Energy Wasted]");
                }
            }
        }

        simulator.jobFinished(jobId, damage * 10 / settings.MaxTimer);
    }

    private AttackResult autoAttack() {
        final double missChance = settings.MISS_CHANCE > catStats.hitPercent / 100 ? settings.MISS_CHANCE - catStats.hitPercent / 100 : 0;
        final double dodgeChance = settings.DODGE_CHANCE > catStats.expertisePercent / 100 ? settings.DODGE_CHANCE - catStats.expertisePercent / 100 : 0;

        double roll = random.nextDouble();

        if ((roll -= missChance) < 0) {
            return AttackResult.MISS;
        }
        if ((roll -= dodgeChance) < 0) {
            return AttackResult.DODGE;
        }
        if ((roll -= settings.GLANCE_CHANCE) < 0) {
            return AttackResult.GLANCE;
        }
        if ((roll -= catStats.critPercent / 100) < 0) {
            return AttackResult.CRIT;
        }
        return AttackResult.HIT;
    }

    private AttackResult yellowAttack() {
        final double missChance = settings.MISS_CHANCE > catStats.hitPercent / 100 ? settings.MISS_CHANCE - catStats.hitPercent / 100 : 0;
        final double dodgeChance = settings.DODGE_CHANCE > catStats.expertisePercent / 100 ? settings.DODGE_CHANCE - catStats.expertisePercent / 100 : 0;
        final double yellowCrit = catStats.critPercent * (1 - missChance - dodgeChance) / 100;

        double roll = random.nextDouble();

        if ((roll -= missChance) < 0) {
            return AttackResult.MISS;
        }
        if ((roll -= dodgeChance) < 0) {
            return AttackResult.DODGE;
        }
        if ((roll -= yellowCrit) < 0) {
            return AttackResult.CRIT;
        }
        return AttackResult.HIT;
    }

    private void yellowCrit() {
        switch (catStats.PrimalFury) {
            case 0:
                comboPoints++;
                break;

            case 1:
                if (random.nextBoolean()) {
                    comboPoints += 2;
                } else {
                    comboPoints++;
                }
                break;

            case 2:
                comboPoints += 2;
                break;
        }
        if (comboPoints > 5) {
            comboPoints = 5;
        }
    }
}
